using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using LitJson;
using UnityEngine;

namespace CGF
{
    class ListLine
    {
        public string abName;
        public string hash;
    }

    class ListFile
    {
        public List<ListLine> lines = new List<ListLine>();

        public ListFile(string content)
        {
            if (!string.IsNullOrEmpty(content))
            {
                string[] lineStrs = content.Split('\n');
                for (int i = 0; i < lineStrs.Length; i++)
                {
                    string lineStr = lineStrs[i];
                    if (string.IsNullOrEmpty(lineStr)) continue;
                    ListLine line = new ListLine();
                    string[] keyValue = lineStr.Split('|');
                    line.abName = keyValue[0];
                    line.hash = keyValue[1].Replace("\n", "").Trim();
                    lines.Add(line);
                }
            }
        }

        public bool ContainsAB(string abName)
        {
            for (int i = 0; i < lines.Count; i++)
            {
                if (lines[i].abName.Equals(abName))
                {
                    return true;
                }
            }
            return false;
        }

        public ListLine GetLine(string abName)
        {
            for (int i = 0; i < lines.Count; i++)
            {
                ListLine line = lines[i];
                if (line.abName.Equals(abName))
                {
                    return line;
                }
            }
            return null;
        }

        public void AddOrUpdateLine(ListLine line)
        {
            ListLine _line = GetLine(line.abName);
            if (_line != null)
            {
                _line.hash = line.hash;
            }
            else
            {
                lines.Add(line);
            }
        }

        public void Save()
        {
            StringBuilder stringBuilder = new StringBuilder(lines.Count);
            for (int i = 0; i < lines.Count; i++)
            {
                ListLine line = lines[i];
                string lineStr = $"{line.abName}|{line.hash}";
                stringBuilder.AppendLine(lineStr);
            }

            if (Define.IsWebGL && !Application.isEditor)
            {
                IndexedDBHelper.SetKeyValue(Define.HashFileName, stringBuilder.ToString());
            }
            else
            {
                string savePath = Define.AbCachePath + "/" + Define.HashFileName;
                File.WriteAllText(savePath, stringBuilder.ToString(), new UTF8Encoding(false));
            }
        }
    }

    class ABDownloadTask
    {
        public string key;
        public CTask task;

        public ABDownloadTask(string key)
        {
            this.key = key;
            task = CTask.Create();
        }
    }

    public class ResManager
    {
        private static ResManager _instance;

        public static ResManager Instance
        {
            get { return _instance ?? (_instance = new ResManager()); }
        }

        ListFile streamList, cacheList, serverList;
        Dictionary<string, AssetBundle> loadedABs = new Dictionary<string, AssetBundle>();
        List<string> preloadABNames = new List<string>();
        int totalPreloadABs;
        Dictionary<string, ABDownloadTask> abDownloadTasks = new Dictionary<string, ABDownloadTask>();

        private ResManager() { }

        #region 下载远程AB

        //开始增量更新远程资源(配表里配在启动时必需下载的部分)
        public async CTask<bool> DownloadAutoUpdateABs(Action<float> onLoadAbProgress)
        {
            if (!CGFConfig.Instance.UseAB || CGFConfig.Instance.ResStrategy == EResStrategy.Resources || CGFConfig.Instance.ResStrategy == EResStrategy.StreamingAssetsOnly)
            {
                return true;
            }

            bool success = await DownloadServerListFile();
            if (!success) return false;
            string autoUpdateAbName = $"{Define.AutoUpdateAbName}{Define.AbExtName}";
            if (CGFConfig.Instance.ResStrategy == EResStrategy.StreamingAssetsOnly || CGFConfig.Instance.ResStrategy == EResStrategy.StreamingAssetsAndRemote)
            {
                await ReadyStreamListFile();
                if (streamList != null && serverList != null && streamList.GetLine(autoUpdateAbName)?.hash == serverList.GetLine(autoUpdateAbName).hash)
                {
                    return true;
                }
            }
            if (CGFConfig.Instance.ResStrategy == EResStrategy.RemoteOnly || CGFConfig.Instance.ResStrategy == EResStrategy.StreamingAssetsAndRemote)
            {
                await ReadyCacheListFile();
                if (cacheList != null && serverList != null && cacheList.GetLine(autoUpdateAbName)?.hash == serverList.GetLine(autoUpdateAbName).hash)
                {
                    return true;
                }
            }

            //默认启动就要下载的AB列表
            string[] dbNames = { $"{Define.AutoUpdateAbName}{Define.AbExtName}" };
            return await DownloadABsByNames(dbNames, onLoadAbProgress);
        }

        /// <summary>
        /// 指定AB包更新(回调参数：标题，进度，是否全部完成)
        /// (abNames为null时，更新所有autoUpdate的资源)
        /// (abName从Prefabs目录开始,输入路径会自动转换为小写加下划线的形式，如："Prefabs/Login")
        /// </summary>
        /// <returns></returns>
        private async CTask<bool> DownloadABsByNames(string[] abNames, Action<float> onLoadAbProgress)
        {
            List<string> needLoadFiles = CheckNeedLoadAbList(abNames);
            if (needLoadFiles.Count > 0)
            {
                for (int i = 0; i < needLoadFiles.Count; i++)
                {
                    Debug.Log($"需要更新AB包:{needLoadFiles[i]}");
                }
            }
            else
            {
                Debug.Log($"以下AB无需更新:{JsonMapper.ToJson(abNames)}");
                return true;
            }

            string taskKey = "";
            for (int i = 0; i < needLoadFiles.Count; i++)
            {
                taskKey += needLoadFiles[i];
            }

            ABDownloadTask abDownloadTask = null;
            if (abDownloadTasks.ContainsKey(taskKey))
            {
                abDownloadTask = abDownloadTasks[taskKey];
            }
            else
            {
                abDownloadTask = new ABDownloadTask(taskKey);
                abDownloadTasks.Add(taskKey, abDownloadTask);

                string folderUrl = $"{CGFConfig.Instance.ResServer}/res/{CGFConfig.Instance.ReleaseArea.ToString()}/{CGFConfig.Instance.Tag}/{CGFConfig.Platform}";
                if (Define.IsWebGL && !Application.isEditor)
                {
                    folderUrl = CGFConfig.Instance.ResServer;
                }
                folderUrl += $"/res_v{CGFConfig.Instance.VersionCode}";
                for (int i = 0; i < needLoadFiles.Count; i++)
                {
                    string abName = needLoadFiles[i];
                    string fileURL = $"{folderUrl}/{abName}";
                    HttpRes res = await HttpHelper.Get(fileURL, null, onLoadAbProgress);
                    if (!res.success)
                    {
                        if (Application.isEditor) Debug.LogError(fileURL);
                        Debug.LogError($"下载AB压缩包失败：{abName} {res.text}");
                        return false;
                    }

                    if (Define.IsWebGL && !Application.isEditor)
                    {
                        IndexedDBHelper.SetKeyValue(abName, res.bytes);
                    }
                    else
                    {
                        if (!Directory.Exists(Define.AbCachePath)) Directory.CreateDirectory(Define.AbCachePath);
                        File.WriteAllBytes(Define.AbCachePath + "/" + abName, res.bytes);
                    }
                }

                AddToLocalList(needLoadFiles);
                await CTask.Wait(0.2f);
                abDownloadTask.task.SetResult();
            }

            await abDownloadTask.task;
            abDownloadTasks.Remove(abDownloadTask.key);
            return true;
        }

        private AssetBundle GetAbFromBytes(byte[] data)
        {
            if (data == null)
            {
                Debug.LogError("data 为 null");
                return null;
            }

            //检查是否带加密头部标记
            bool isEncrypted = true;
            int headLen = 5;
            for (int i = 0; i < headLen; i++)
            {
                if (data[i] != 0)
                {
                    isEncrypted = false;
                    break;
                }
            }

            if (isEncrypted)
            {
                //去掉头部标记
                byte[] dataNoHead = new byte[data.Length - headLen];
                for (int i = 0; i < dataNoHead.Length; i++)
                {
                    dataNoHead[i] = data[i + headLen];
                }

                byte[] abDataDe = AESHelper.DecryptBytes(dataNoHead, MD5Helper.StringToMD5("GameBigAssets"));
                return AssetBundle.LoadFromMemory(abDataDe);
            }

            return AssetBundle.LoadFromMemory(data);
        }

        /// <summary>
        /// 判断某个AB是否已经下载（输入 Prefabs/Activities/DailyReward 或 prefabs_activities_dailyreward）
        /// </summary>
        /// <returns></returns>
        public bool IsAbLoaded(string abName)
        {
            if (!CGFConfig.Instance.UseAB || CGFConfig.Instance.ResStrategy == EResStrategy.StreamingAssetsOnly || CGFConfig.Instance.ResStrategy == EResStrategy.Resources)
            {
                return true;
            }
            if (serverList != null)
            {
                ListLine serverLine = serverList.GetLine(abName);
                if (serverLine == null)
                {
                    Debug.LogError($"serverList中不存在{abName}");
                    return false;
                }
                if (cacheList != null)
                {
                    ListLine cacheLine = cacheList.GetLine(abName);
                    if (cacheLine != null && serverLine.hash.Equals(cacheLine.hash))
                    {
                        return true;
                    }
                }
                if (streamList != null)
                {
                    ListLine streamLine = streamList.GetLine(abName);
                    if (streamLine != null && serverLine.hash.Equals(streamLine.hash))
                    {
                        return true;
                    }
                }
            }
            return false;
        }

        private async CTask<bool> DownloadServerListFile()
        {
            if (serverList == null)
            {
                if (CGFConfig.Instance.ResStrategy == EResStrategy.RemoteOnly || CGFConfig.Instance.ResStrategy == EResStrategy.StreamingAssetsAndRemote)
                {
                    string serverListUrl = $"{CGFConfig.Instance.ResServer}/res/{CGFConfig.Instance.ReleaseArea.ToString()}/{CGFConfig.Instance.Tag}/{CGFConfig.Platform}/res_v{CGFConfig.Instance.VersionCode}/{Define.HashFileName}";
                    if (Define.IsWebGL && !Application.isEditor)
                    {
                        serverListUrl = $"{CGFConfig.Instance.ResServer}/res_v{CGFConfig.Instance.VersionCode}/{Define.HashFileName}";
                    }
                    HttpRes res = await HttpHelper.Get(serverListUrl);
                    if (!res.success)
                    {
                        Debug.LogError($"下载远程{Define.HashFileName}文件失败[{serverListUrl}]{res.text}");
                        return false;
                    }
                    serverList = new ListFile(res.text);
                }
            }
            return true;
        }

        private async CTask ReadyStreamListFile()
        {
            if (streamList == null && (CGFConfig.Instance.ResStrategy == EResStrategy.StreamingAssetsOnly || CGFConfig.Instance.ResStrategy == EResStrategy.StreamingAssetsAndRemote))
            {
                string content = null;
                byte[] bytes = await StreamingAssetsHelper.GetBytes(Define.StreamingPathAbFolder + "/" + Define.HashFileName);
                if (bytes != null)
                {
                    content = Encoding.UTF8.GetString(bytes);
                }
                Debug.Log($"StreamListFile:{content}");
                if (!string.IsNullOrEmpty(content))
                {
                    streamList = new ListFile(content);
                }
            }
        }

        private async CTask ReadyCacheListFile()
        {
            if (cacheList == null)
            {
                string content = null;
                if (Define.IsWebGL && !Application.isEditor)
                {
                    CTask<string> task = CTask<string>.Create();
                    IndexedDBHelper.GetValueByKey(Define.HashFileName, content =>
                    {
                        Debug.Log($"IndexedDBHelper.LoadString: {content}");
                        task.SetResult(content);
                    });
                    content = await task;
                }
                else
                {
                    string localListPath = Define.AbCachePath + "/" + Define.HashFileName;
                    if (File.Exists(localListPath))
                    {
                        content = File.ReadAllText(localListPath);
                    }
                }
                Debug.Log($"CacheListFile:{content}");
                cacheList = new ListFile(content);
            }
        }

        private List<string> CheckNeedLoadAbList(string[] abNames)
        {
            List<string> requestAbList = new List<string>();
            for (int i = 0; i < abNames.Length; i++)
            {
                string abName = abNames[i];
                if (!serverList.ContainsAB(abName))
                {
                    Debug.LogError($"服务器{Define.HashFileName}中找不到{abName}");
                }
                requestAbList.Add(abName);
            }

            List<string> needLoadFiles = new List<string>();
            for (int i = 0; i < requestAbList.Count; i++)
            {
                string abName = requestAbList[i];
                if (!IsAbLoaded(abName) && !needLoadFiles.Contains(abName))
                {
                    needLoadFiles.Add(abName);
                }
            }
            return needLoadFiles;
        }

        #endregion

        #region 预加载本地AB

        /// <summary>
        /// 预下载AB列表，传入目录路径
        /// </summary>
        public async CTask<bool> PreloadLocalABs(string[] assetPaths, Action<float> onProgress)
        {
            if (!CGFConfig.Instance.UseAB || CGFConfig.Instance.ResStrategy == EResStrategy.Resources || CGFConfig.Instance.ResStrategy == EResStrategy.StreamingAssetsOnly)
            {
                onProgress?.Invoke(1f);
                return true;
            }

            CTask<bool> task = CTask<bool>.Create();
            preloadABNames.Clear();
            for (int i = 0; i < assetPaths.Length; i++)
            {
                ParseAssetPath(assetPaths[i], out string abName, out string assetName);
                if (!preloadABNames.Contains(abName)) preloadABNames.Add(abName);
            }

            totalPreloadABs = preloadABNames.Count;
            PreloadAB(task, onProgress);
            await task;
            return task.Result;
        }

        #endregion

        #region 加载AB包里的资源

        /// <summary>
        /// 异步获取资源(assetPath不带后缀名)
        /// </summary>
        public async CTask<T> LoadAsset<T>(string assetPath, bool unloadABAfterSpawn = false) where T : UnityEngine.Object
        {
            if (typeof(T) != typeof(TextAsset) && typeof(T) != typeof(GameObject))
            {
                Debug.LogError($"Transform和RectTransform类型不支持，请使用GameObject代替");
                return null;
            }

            assetPath = assetPath.Replace(Define.BundleFolder, "");
            if (assetPath.StartsWith("/")) assetPath = assetPath.Substring(1);

            if (CGFConfig.Instance.ResStrategy == EResStrategy.Resources)
            {
                return Resources.Load<T>(assetPath);
            }

            if (CGFConfig.Instance.UseAB)
            {
                ParseAssetPath(assetPath, out string abName, out string assetName);
                Debug.Log($"assetPath: {assetPath} abName: {abName} assetName: {assetName}");
                return await LoadAssetFromAB<T>(abName, assetName, unloadABAfterSpawn);
            }
            else
            {
#if UNITY_EDITOR
                string bundleFolder = CGFConfig.Instance.ResStrategy == EResStrategy.Resources ? "Resources" : Define.BundleFolder;
                string prefabFullPath = AddSuffix(Application.dataPath + "/" + bundleFolder + "/" + assetPath);
                if (string.IsNullOrEmpty(prefabFullPath))
                {
                    Debug.LogError($"找不到 {prefabFullPath} 资源");
                    return null;
                }

                prefabFullPath = prefabFullPath.Substring(prefabFullPath.IndexOf("Assets/"));
                T asset = UnityEditor.AssetDatabase.LoadAssetAtPath<T>(prefabFullPath);
                return asset;
#else
                return null;
#endif
            }
        }

        #endregion

        #region 内部私有方法

        /// <summary>
        /// 自动查找文件加上后缀名
        /// </summary>
        private static string AddSuffix(string assetPath)
        {
            string path = assetPath.Substring(0, assetPath.LastIndexOf('/'));
            string name = assetPath.Substring(assetPath.LastIndexOf('/') + 1);
            string[] files = Directory.GetFiles(path, name + ".*", SearchOption.TopDirectoryOnly);
            for (int i = 0; i < files.Length; i++)
            {
                string _path = files[i];
                if (!_path.EndsWith(".meta"))
                {
                    return _path;
                }
            }
            return null;
        }

        /// <summary>
        /// 从AB里异步获取资源
        /// </summary>
        private async CTask<T> LoadAssetFromAB<T>(string abName, string assetName, bool unloadABAfterSpawn = false) where T : UnityEngine.Object
        {
            if (!abName.EndsWith(Define.AbExtName))
            {
                abName += Define.AbExtName;
            }

            T asset;
            if (loadedABs.TryGetValue(abName, out AssetBundle ab))
            {
                asset = ab.LoadAsset<T>(assetName);
                if (asset == default(T))
                {
                    Debug.LogError($"ab包[{abName}]中没有{assetName}对象");
                }
                if (unloadABAfterSpawn)
                {
                    ab.Unload(false);
                    loadedABs.Remove(abName);
                }
                return asset;
            }

            if (CGFConfig.Instance.ResStrategy == EResStrategy.StreamingAssetsOnly)
            {
                byte[] abBytes = await StreamingAssetsHelper.GetBytes($"{Define.StreamingPathAbFolder}/{abName}");
                if (abBytes == null || abBytes.Length == 0)
                {
                    Debug.LogError($"AB包未导出到StreamingPath：{abName}");
                    return default(T);
                }
                ab = GetAbFromBytes(abBytes);
            }
            else
            {
                bool loadABSuccess = false;
                if (Define.IsWebGL && !Application.isEditor)
                {
                    CTask<byte[]> task = CTask<byte[]>.Create();
                    IndexedDBHelper.GetValueByKey(abName, bytes =>
                    {
                        task.SetResult(bytes);
                    });
                    byte[] bytes = await task;
                    if (bytes != null)
                    {
                        ab = GetAbFromBytes(bytes);
                        loadABSuccess = true;
                    }
                    else
                    {
                        Debug.LogError($"从IndexedDB里读取{abName}失败，可能资源不存在");
                    }
                }
                else
                {
                    string localPath = Define.AbCachePath + "/" + abName;
                    if (File.Exists(localPath))
                    {
                        HttpRes res = await HttpHelper.Get("file://" + localPath);
                        if (!res.success)
                        {
                            Debug.LogError(" [ " + localPath + " ] " + res.text);
                            return null;
                        }
                        ab = GetAbFromBytes(res.bytes);
                        loadABSuccess = true;
                    }
                    else
                    {
                        Debug.LogError($"本地缓存里没有{abName},继续StreamingAssets里找");
                    }
                }
                if (!loadABSuccess)
                {
                    byte[] abBytes = await StreamingAssetsHelper.GetBytes($"{Define.StreamingPathAbFolder}/{abName}");
                    if (abBytes == null || abBytes.Length == 0)
                    {
                        Debug.LogError($"AB包未下载：{abName}");
                        return default(T);
                    }
                    ab = GetAbFromBytes(abBytes);
                }
            }

            asset = ab.LoadAsset<T>(assetName);
            if (asset == default(T))
            {
                /*foreach (var item in ab.LoadAllAssets())
                {
                    Debug.LogError($"ab包[{abName}]:{item}");
                }*/
                Debug.LogError($"ab包{abName}中没有{assetName}对象");
            }

            if (unloadABAfterSpawn)
            {
                ab.Unload(false);
            }
            else
            {
                loadedABs.Add(abName, ab);
            }

            return asset;
        }

        private static void ParseAssetPath(string assetPath, out string abName, out string assetName)
        {
            abName = $"{Define.AutoUpdateAbName}{Define.AbExtName}";
            foreach (var extraFolder in CGFConfig.Instance.extraUpdateFolders)
            {
                string extraPath = extraFolder.path.Replace($"Assets/{Define.BundleFolder}/", "");
                if (assetPath.Contains(extraPath))
                {
                    abName = extraPath.Replace("/", "_").ToLower() + Define.AbExtName;
                }
            }
            assetName = assetPath;
            if (assetPath.Contains("/"))
            {
                assetName = assetPath.Substring(assetPath.LastIndexOf("/") + 1);
            }
        }

        private async void PreloadAB(CTask<bool> task, Action<float> onProgress)
        {
            while (preloadABNames.Count > 0)
            {
                string abName = preloadABNames[0];
                preloadABNames.RemoveAt(0);
                AssetBundle ab = null;
                if (!loadedABs.TryGetValue(abName, out ab))
                {
                    string localUrl = "file://" + Define.AbCachePath + "/" + abName;
                    if (CGFConfig.Instance.ResStrategy == EResStrategy.StreamingAssetsOnly)
                    {
                        localUrl = $"{Define.StreamPathUrl}/{Define.StreamingPathAbFolder}/{abName}";
                    }

                    HttpRes res = await HttpHelper.Get(localUrl);
                    if (!res.success)
                    {
                        Debug.LogError(" [ " + localUrl + " ] " + res.text);
                        task.SetResult(false);
                        return;
                    }

                    ab = GetAbFromBytes(res.bytes);
                    loadedABs.Add(abName, ab);
                }

                onProgress?.Invoke((totalPreloadABs - preloadABNames.Count) / (float)totalPreloadABs);
            }

            task.SetResult(true);
        }

        private void AddToLocalList(List<string> needLoadFiles)
        {
            foreach (var item in needLoadFiles)
            {
                ListLine line = serverList.GetLine(item);
                cacheList.AddOrUpdateLine(line);
            }
            cacheList.Save();
        }

        #endregion
    }
}
